/* COPYRIGHT_CHUNFENG */

#include <sys/select.h>
#include <unistd.h>
#include "errno.h"
#include "cf_std.h"
#include "cf_common.h"
#include "lcf_log.h"
#include "cf_fdevent.h"
#include "cf_list.h"
	
CFFdevent* cf_fdevent_new(int fd, 
					read_action_fun read_action, void* read_parameter,
					write_action_fun write_action, void* write_parameter)
{
	CFFdevent* new_event;

	if(read_action && write_action){
		LCF_ERR("Read and write action can not specify at the same time.");
		return NULL;
	}
	new_event = (CFFdevent*)malloc(sizeof(CFFdevent));
	if(!new_event){
		LCF_ERR("malloc for CFFdevent failed\n");
		return NULL;
	}
	cf_fdevent_init(new_event, fd, read_action, read_parameter, write_action, write_parameter);
	return new_event;
}

int cf_fdevent_init(CFFdevent* self, int fd, 
					read_action_fun read_action, void* read_parameter,
					write_action_fun write_action, void* write_parameter)
{
	if(read_action && write_action){
		LCF_ERR("Read and write action can not specify at the same time.");
		return -1;
	}
	memset(self, 0, sizeof(CFFdevent));
	self->fd = fd;
	self->read_action = read_action;
	self->read_parameter = read_parameter;
	self->write_action = write_action;
	self->write_parameter = write_parameter;
    cf_list_init(&self->list);
	return 0;
}

int cf_fdevent_exit(CFFdevent* this)
{
    return 0;
}

CFFdevent* cf_fdevent_get_next(CFFdevent* self)
{
    CFListHead* list;
    list = cf_list_get_next(&self->list);
    if(list)
        return container_of(list, CFFdevent, list);
    else
        return NULL;
}

inline int cf_fdevent_free(CFFdevent* self)
{
	free(self);
	return 0;
}

int cf_fdevents_init(CFFdevents* this)
{
	memset(this, 0, sizeof(CFFdevents));
    return cf_fdevent_init(&this->event_head, -1, NULL, NULL, NULL, NULL);  /* dummy head */  
}

int cf_fdevents_exit(CFFdevents* this)
{
    return 0;
}

CFFdevents* cf_fdevents_new()
{
	CFFdevents* new_events;
	new_events = (CFFdevents*)malloc(sizeof(CFFdevents));
	if(!new_events){
		LCF_ERR("malloc for CFFdevents failed\n");
		return NULL;
	}
	memset(new_events, 0, sizeof(CFFdevents));
    cf_fdevent_init(&new_events->event_head, -1, NULL, NULL, NULL, NULL);  /* dummy head */
	return new_events;
}

int cf_fdevents_free(CFFdevents* self)
{
    free(self);
    return 0;
}

int cf_fdevents_free_all(CFFdevents* self)
{
    CFListContainerForeachSafe(&self->event_head, tmpEvent, CFFdevent, list){
        cf_fdevent_free(tmpEvent);
    }
    cf_fdevent_init(&self->event_head, -1, NULL, NULL, NULL, NULL);    
    return 0;
}

int cf_fdevents_free_all_self(CFFdevents* self)
{
    cf_fdevents_free_all(self);
    free(self);
	return 0;
}

int cf_fdevents_add(CFFdevents* self, CFFdevent* new_event)
{
    cf_list_insert_pre(&self->event_head.list, &new_event->list);
	return 0;
}

int cf_fdevents_del(CFFdevents* this, CFFdevent* event)
{
    cf_list_del(&event->list);
    return 0;
}

int cf_fdevents_del_all(CFFdevents* this)
{
    CFListContainerForeachSafe(&this->event_head, tmpEvent, CFFdevent, list){
        cf_fdevents_del(this, tmpEvent);
    }
    return 0;
}

int cf_fdevents_del_close(CFFdevents* this, CFFdevent* event)
{
    cf_list_del(&event->list);
    close(event->fd);
    return 0;
}

int cf_fdevents_del_close_free(CFFdevents* this, CFFdevent* event)
{
    cf_list_del(&event->list);
    close(event->fd);
    cf_fdevent_free(event);
    return 0;
}

int cf_fdevents_del_free(CFFdevents* self, CFFdevent* event_to_del)
{
    cf_list_del(&event_to_del->list);
	cf_fdevent_free(event_to_del);
	return 0;
}

int cf_fdevents_trigger(CFFdevents* self)
{
	fd_set read_fdset, write_fdset;
	int max_fd = -1;
	CFFdevent* tmpEvent;
	int sel_ret;
    CFEventAction actionHead;
	
    
    cf_event_action_init(&actionHead, NULL, NULL, NULL, -1, NULL);
	while(1){
		FD_ZERO(&read_fdset);
		FD_ZERO(&write_fdset);
		tmpEvent = cf_fdevent_get_next(&self->event_head);
		if(tmpEvent == &self->event_head){
			LCF_DBG("No fd to monitor; Exit event loop.\n");
			return 0;
		}
		while(tmpEvent != &self->event_head){
			max_fd = (max_fd > tmpEvent->fd ? max_fd : tmpEvent->fd);
			if(tmpEvent->read_action){	
				FD_SET(tmpEvent->fd, &read_fdset);
			}
			if(tmpEvent->write_action){
				FD_SET(tmpEvent->fd, &write_fdset);
			}
			tmpEvent = cf_fdevent_get_next(tmpEvent);
		}
		sel_ret = select(max_fd + 1, &read_fdset, &write_fdset, NULL, NULL);
		if(sel_ret == -1){
            if(errno == EINTR){
                LCF_DBG("Interrput caused the select exit, We can handle it with cf_sigfd!!\n");
				continue;
            }else{
                return -1;
            }
		}else if(sel_ret > 0){
            tmpEvent = cf_fdevent_get_next(&self->event_head);
			while(tmpEvent != &self->event_head){
				if(tmpEvent->read_action && FD_ISSET(tmpEvent->fd, &read_fdset)){
                    cf_event_actions_safe_add(
                        &actionHead, 
                        cf_event_action_new(
                            tmpEvent->read_action, 
                            self,
                            tmpEvent,
                            tmpEvent->fd, 
                            tmpEvent->read_parameter
                        )
                    );
				}else if(tmpEvent->write_action && FD_ISSET(tmpEvent->fd, &write_fdset)){
                    cf_event_actions_safe_add(
                        &actionHead, 
                        cf_event_action_new(
                            tmpEvent->write_action,
                            self,
                            tmpEvent,
                            tmpEvent->fd, 
                            tmpEvent->write_parameter
                        )
                    );
			    }
			    tmpEvent = cf_fdevent_get_next(tmpEvent);
			}
			cf_event_actions_go(&actionHead);
			cf_event_actions_free(&actionHead);
		}
	}
}

int cf_event_action_init(CFEventAction* new_action, int(*action)(CFFdevents* events, CFFdevent* event, int fd, void* context), CFFdevents* events, CFFdevent* event, int fd, void* context)
{
    new_action->events = events;
    new_action->event = event;
    new_action->action = action;
    new_action->fd = fd;
    new_action->context = context;
    cf_list_init(&new_action->list);
    return 0;	
}

CFEventAction* cf_event_action_new(int(*action)(CFFdevents* events, CFFdevent* event, int fd, void* context), CFFdevents* events, CFFdevent* event, int fd, void* context)
{
    CFEventAction* new_action = (CFEventAction*)malloc(sizeof(CFEventAction));
    if(!new_action){
        LCF_ERR("malloc for new event action failed\n");
        return NULL;
    }
	cf_event_action_init(new_action, action, events, event, fd, context);
    return new_action;
}

int cf_event_action_free(CFEventAction* action)
{
    free(action);
    return 0;
}

int cf_event_actions_add(CFEventAction* head, CFEventAction* to_add)
{
    cf_list_insert_pre(&head->list, &to_add->list);
    return 0;
}

CFEventAction* cf_event_actions_safe_add(CFEventAction* head, CFEventAction* to_add)
{
    if(!head)
        return to_add;
    else{
        cf_list_insert_pre(&head->list, &to_add->list);
        return head;
    }
}

int cf_event_actions_del(CFEventAction* head, CFEventAction* to_del)
{
    return cf_list_del(&to_del->list);
}

CFEventAction* cf_event_action_get_next(CFEventAction* action)
{
    CFListHead* list = cf_list_get_next(&action->list);
    if(!list)
        return NULL;
    else
        return container_of(list, CFEventAction, list);
}

int cf_event_actions_go(CFEventAction* head)
{
    CFEventAction* tmpAction = head;
    for(tmpAction = cf_event_action_get_next(head); (tmpAction!=head)&&tmpAction; tmpAction = cf_event_action_get_next(tmpAction)){
        tmpAction->action(tmpAction->events, tmpAction->event, tmpAction->fd, tmpAction->context);
    }
    return 0;
}

int cf_event_actions_free(CFEventAction* head)
{
    CFListContainerForeachSafe(head, tmpAction, CFEventAction, list){
        cf_event_action_free(tmpAction);
    }
    cf_event_action_init(head, NULL, NULL, NULL, -1, NULL);
    return 0;    
}
